# -*- coding: UTF-8 -*-

# Copyright (C) 2009 Daniele Favara.
# Written by Daniele Favara <daniele@zeroisp.com>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


# Defining metadata:

__date__ = "$Date$"

import re

from sqlalchemy.types import TypeDecorator, String, Unicode

from tgext.zeroisp.model import ModelError
from tgext.zeroisp.model.zvalidators import *
from tgext.zeroisp.model.zvalidators import __all__ as validation_objects

__all__ = ['EContactAddress', 'Email', 'Enum', 'JabberId', 'Locale', \
           'WebURL', 'InvalidSetError'] + validation_objects


#{ Type definition exceptions

class InvalidSetError(ModelError):
    """Exception raised when given a bad set for the definition of a
    L{Enum} field."""
    pass


#{ The special types

class Enum(TypeDecorator):
    """
    Emulate an Enum type.

    This type definition is based on the code found at:
    U{http://www.sqlalchemy.org/trac/wiki/UsageRecipes/Enum}

    """
    impl = String

    def __init__(self, values):
        """Emulate an Enum type.

        @param values: The set of valid values for this type.
        @type values: An iterable object
        @raise InvalidSetError: If C{values} is not an iterable object or
            contains less than 2 items.

        """
        if values is None or len(values) < 2 or not hasattr(values,'__iter__'):
            raise InvalidSetError('Enum requires a list of values')

        self.validator = EnumValidator(values[:])

        # The length of the column should be the longest string in values
        size = max([len(v) for v in values if v is not None])
        super(Enum, self).__init__(size)

    def process_bind_param(self, value, dialect):
        """Return C{value} if and only if it's included in the set.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @raise NotInEnumError: If C{value} is not in the set.

        """
        return self.validator.to_python(value, None)

    def process_result_value(self, value, dialect):
        """Return C{value} is it's included in the set or if it's an empty
        string.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @return: C{value}, transformed into None if it's an empty string.
        @rtype: C{str} or C{None}
        @raise NotInEnumError: If C{value} is not in the set and is not an
            empty string either.

        """
        if value is '':
            return None
        return self.validator.to_python(value, None)


class Locale(TypeDecorator):
    """Represent a locale string."""
    impl = String

    validator = LocaleValidator()
    """@cvar: The pattern of a valid locale string."""

    def __init__(self):
        """Represent a locale string."""
        # A locale may have up to 5 characters
        super(Locale, self).__init__(5)

    def process_bind_param(self, locale, dialect):
        """Return C{locale} if and only if it's a valid locale string.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @raise InvalidLocaleError: If C{locale} is not a locale.

        """
        # If it's a locale, return its value:
        return self.__class__.validator.to_python(locale)

    def process_result_value(self, locale, dialect):
        """Return C{locale} is it's a locale or if it's an empty string.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @return: C{locale}, transformed into None if it's an empty string.
        @rtype: C{str} or C{None}
        @raise InvalidLocaleError: If C{locale} is not a locale nor an empty
            string.

        """
        if locale is '':
            return None
        # If it's a locale, return its value:
        return self.__class__.validator.to_python(locale)


class Email(TypeDecorator):
    """Represent a email address."""
    impl = Unicode

    email_validator = EmailValidator(resolve_domain=False)
    """@cvar: The email validator."""

    def __init__(self):
        """Represent a email."""
        super(Email, self).__init__(255)

    def process_bind_param(self, email, dialect):
        """Return C{email} if and only if it's a valid email address.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @raise InvalidEmailError: If C{email} is not an email according to
            L{EmailValidator}.

        """
        if email is None:
            return ''
        # If it's a email, return its value:
        return self.__class__.email_validator.to_python(email)

    def process_result_value(self, email, dialect):
        """Return C{email} is it's a valid email address or if it's an empty
        string.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @return: C{email}, transformed into None if it's an empty string.
        @rtype: C{str} or C{None}
        @raise InvalidEmailError: If C{email} is not a valid email (according
            to L{EmailValidator}) nor an empty string.

        """
        if not email :
            return None
        # If it's an email address, return its value:
        return self.__class__.email_validator.to_python(email)


class JabberId(Email):
    """Represent a Jabber Id."""
    pass


class WebURL(TypeDecorator):
    """Represent the URL of an HTTP/HTTPS resource."""
    impl = Unicode

    validator = URLValidator()

    def __init__(self):
        """Represent a web URL."""
        super(WebURL, self).__init__(255)

    def process_bind_param(self, url, dialect):
        """Return C{url} if and only if it's a valid web address.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @raise InvalidURLError: If C{url} is not a valid web address.

        """
        # If it's a email, return its value:
        return self.__class__.validator.to_python(url)

    def process_result_value(self, url, dialect):
        """Return C{url} is it's included in the set or if it's an empty
        string.

        @see: U{http://www.sqlalchemy.org/docs/05/types.html#types_custom}
        @return: C{url}, transformed into None if it's an empty string.
        @rtype: C{str} or C{None}
        @raise InvalidURLError: If C{url} is not a valid web URL or an empty
            string.

        """
        if url is '':
            return None
        # If it's a web address, return its value:
        return self.__class__.validator.to_python(url)


class EContactAddress(TypeDecorator):
    """An electronic contact address.

    An electronic contact account may be an email or Jabber account, for
    example. In this case, the corresponding electronic contact address would
    be the email address or the Jabber Id.

    Examples of valid electronic contact addresses:
     - C{mailto:bob@example.com}, for an email account.
     - C{xmpp:bob@example.org}, for a Jabber account.

    """
    impl = Unicode

    validator = ContactAddressValidator()

    def __init__(self):
        """Represent an electronic contact address."""
        super(EContactAddress, self).__init__(255)

    def process_bind_param(self, address, dialect):
        """Return C{address} if and only if it's a valid address.

        @return: The address.
        @rtype: C{str}
        @raise InvalidContactAddressError: If it is not valid according to
            L{ContactAddressValidator}.

        """
        # If it's a valid address, then return its value
        return self.__class__.validator.to_python(address)

    def process_result_value(self, address, dialect):
        """Return the address if it's valid or an empty string.

        @rtype: C{str} or C{None}
        @raise InvalidContactAddressError: If it is not valid according to
            L{ContactAddressValidator} and is not an empty string either.

        """
        if address is '':
            return None
        # If it's a valid address, then return its value
        return self.__class__.validator.to_python(address)


#}
